#include<cstdlib>
#include<iostream>
#include<fstream>
#include<string>
#include<sstream>
#include<vector>
#include<map>
#include<set>

#include<boost/format.hpp>

#include<formic/exception.h>

std::string make_capital(const std::string & str_in) {

  std::map<char,char> letter_map;
  letter_map['a'] = 'A';
  letter_map['b'] = 'B';
  letter_map['c'] = 'C';
  letter_map['d'] = 'D';
  letter_map['e'] = 'E';
  letter_map['f'] = 'F';
  letter_map['g'] = 'G';
  letter_map['h'] = 'H';
  letter_map['i'] = 'I';
  letter_map['j'] = 'J';
  letter_map['k'] = 'K';
  letter_map['l'] = 'L';
  letter_map['m'] = 'M';
  letter_map['n'] = 'N';
  letter_map['o'] = 'O';
  letter_map['p'] = 'P';
  letter_map['q'] = 'Q';
  letter_map['r'] = 'R';
  letter_map['s'] = 'S';
  letter_map['t'] = 'T';
  letter_map['u'] = 'U';
  letter_map['v'] = 'V';
  letter_map['w'] = 'W';
  letter_map['x'] = 'X';
  letter_map['y'] = 'Y';
  letter_map['z'] = 'Z';

  std::string retval(str_in);
  for (std::string::iterator c = retval.begin(); c != retval.end(); c++)
    if ( letter_map.count(*c) > 0 )
      *c = letter_map[*c];

  return retval;

}

class InputOptionItem {

  private:

    bool _derived;
    std::string _name;
    std::string _type;
    std::string _short_type;
    std::string _const_func;
    std::string _default;
    std::string _description;
    std::vector<std::string> _tests;
    std::vector<std::string> _derivation_code_lines;

    static std::set<std::string> _names;
    static std::map<std::string, std::string> option_type;

    void read_prop(std::ifstream & ifs, std::string & prop, const std::string & prop_name) {
      prop.clear();
      std::getline(ifs, prop);
      std::stringstream ss(prop);
      std::string stripped;
      if ( !(ss >> stripped) )
        throw formic::Exception("Error: failed to read %s of variable with name = %s") % prop_name % _name;
    }

    std::string name_replace(const std::string & str_in, const bool printout_style = false) const {

      std::string str(str_in);
      std::string retval;

      while (str.size() > 0) {

        if ( str.at(0) == '$' ) {

          int i = 1;
          while (i < str.size() && allowed_characters.count(str.at(i)) > 0)
            i++;
          std::string word = str.substr(1, i-1);
          str.erase(0, i);
          if ( _names.count(word) > 0 && !printout_style )
            word = (boost::format("(_%s_map[\"%s\"])") % get_short_type[option_type[word]] % word).str();
          else if ( !printout_style )
            word = std::string("$") + word;
          retval = retval + word;

        } else {

          int i = 1;
          while (i < str.size() && str.at(i) != '$')
            i++;
          std::string word = str.substr(0, i);
          str.erase(0, i);
          retval = retval + word;

        }

      }

      return retval;

    }

  public:

    class NoMoreOptions : public std::runtime_error {

      public:

        NoMoreOptions(const std::string & message = std::string()) : std::runtime_error(message) {}
    
    };

    static std::map<std::string, std::string> get_short_type;
    static std::map<std::string, std::string> get_const_func;
    static std::set<char> allowed_characters;

    InputOptionItem(std::ifstream & ifs) {

      // get the next line that is not just whitespace
      std::string line;
      for( std::string stripped; stripped.size() == 0; ) {
        if ( !ifs || ifs.eof() )
          throw InputOptionItem::NoMoreOptions();
        std::getline(ifs, line);
        std::stringstream ss(line);
        if ( !(ss >> stripped) )
          stripped.clear();
      }

      // get name
      {
        std::stringstream ss(line);
        ss >> _name;
        if ( _names.count(_name) > 0 )
          throw formic::Exception("An option with the name \"%s\" has already been declared.") % _name;
        _names.insert(_name);
      }

      // get type, default value, and description
      this->read_prop(ifs, _type, "type");
      this->read_prop(ifs, _default, "default value");
      this->read_prop(ifs, _description, "description");

      // check whether this is a derived option
      if ( _type.size() >= 7 && _type.substr(0, 7) == "derived" ) {
        _derived = true;
        _type.erase(0, 7);
        while (_type.size() > 0 && _type.at(0) == ' ')
          _type.erase(0, 1);
      } else {
        _derived = false;
      }

      // save the type
      option_type[_name] = _type;

      // get short type
      if ( get_short_type.count(_type) > 0 )
        _short_type = get_short_type[_type];
      else
        throw formic::Exception("Input framework does not support options of type \"%s\"") % _type;

      // get constructor function
      if ( get_const_func.count(_type) > 0 )
        _const_func = get_const_func[_type];
      else
        throw formic::Exception("Input framework does not support options of type \"%s\"") % _type;

      // get any tests
      bool more_tests = true;
      do {
        std::getline(ifs, line);
        std::stringstream ss(line);
        std::string stripped;
        if ( !(ss >> stripped) )
          more_tests = false;
        else
          _tests.push_back(line);
      } while (more_tests);

      // get the derivation code lines
      bool more_der_lines = true;
      while ( _derived && more_der_lines) {
        std::getline(ifs, line);
        std::stringstream ss(line);
        std::string stripped;
        if ( !(ss >> stripped) )
          more_der_lines = false;
        else
          _derivation_code_lines.push_back(this->name_replace(line));
      }

    }

    void write_header_declaration(std::ofstream & ofs, const std::string & indent) const {
      ofs << indent << boost::format("/// \\brief  %s") % _description << std::endl;
      ofs << indent << boost::format("%s _%s;") % _type % _name << std::endl;
      ofs << std::endl;
    }

    void write_header_function(std::ofstream & ofs, const std::string & indent) const {
      ofs << indent << boost::format("/// \\sa _%s") % _name << std::endl;
      ofs << indent << boost::format("const %s & %s() const { return _%s; }") % _type % _name % _name << std::endl;
      ofs << std::endl;
    }

    void write_cpp_default(std::ofstream & ofs, const std::string & indent) const {
      ofs << indent << boost::format("_%s_map[\"%s\"] = %s( %s );") % _short_type % _name % _const_func % _default << std::endl;
    }

    void write_cpp_tests(std::ofstream & ofs, const std::string & indent) const {
      ofs << indent << boost::format("{") << std::endl;
      //ofs << indent << boost::format("  const %s & %s = _%s_map[\"%s\"];") % _type % _name % _short_type % _name << std::endl;
      for (int i = 0; i < _tests.size(); i++) {
        ofs << indent << boost::format("  if ( !( %s ) )") % this->name_replace(_tests.at(i)) << std::endl;
        std::string test_for_printing = this->name_replace(_tests.at(i), true);
        for (int j = 0; j < test_for_printing.size(); j++)
          if ( test_for_printing.at(j) == '"' )
            test_for_printing.replace(j++, 1, "\\\"");
        ofs << indent << boost::format("    throw formic::Exception(\"input option test ( %s ) failed\");") % test_for_printing << std::endl;
      }
      ofs << indent << boost::format("}") << std::endl;
      ofs << std::endl;
    }

    void write_cpp_derivation(std::ofstream & ofs, const std::string & indent) const {
      ofs << indent << boost::format("{") << std::endl;
      //ofs << indent << boost::format("  %s & %s = _%s_map[\"%s\"];") % _type % _name % _short_type % _name << std::endl;
      for (int i = 0; i < _derivation_code_lines.size(); i++)
        ofs << indent << "  " << _derivation_code_lines.at(i) << std::endl;
      ofs << indent << boost::format("  _%s = _%s_map[\"%s\"];") % _name % _short_type % _name << std::endl;
      ofs << indent << boost::format("}") << std::endl;
      ofs << std::endl;
    }

    void write_cpp_set_member(std::ofstream & ofs, const std::string & indent) const {
      ofs << indent << boost::format("else if (member_name == \"%s\")") % _name << std::endl;
      ofs << indent << boost::format("  this->InputBase::set_member(member_name, arch, _%s);") % _name << std::endl;
      ofs << std::endl;
    }

    void write_cpp_print(std::ofstream & ofs, const std::string & indent) const {
      ofs << indent << boost::format("{") << std::endl;
      ofs << indent << boost::format("  formic::TextArchive arch;") << std::endl;
      ofs << indent << boost::format("  arch << \"Option   %s %s \" << _%s_map[\"%s\"];")
                       % _name
                       % std::string(std::max(0, 20-int(_name.size())), ' ')
                       % _short_type
                       % _name
                    << std::endl;
      ofs << indent << boost::format("  ss << arch.str() << std::endl;") << std::endl;
      ofs << indent << boost::format("}") << std::endl;
      ofs << std::endl;
    }

};

std::map<std::string, std::string> InputOptionItem::get_short_type;

std::map<std::string, std::string> InputOptionItem::get_const_func;

std::map<std::string, std::string> InputOptionItem::option_type;

std::set<char> InputOptionItem::allowed_characters;

std::set<std::string> InputOptionItem::_names;

int main(const int argc, const char ** argv) {

  {
    const std::string allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
    InputOptionItem::allowed_characters.clear();
    InputOptionItem::allowed_characters.insert(allowed.begin(), allowed.end());
  }

  InputOptionItem::get_short_type["bool"] = "bool";
  InputOptionItem::get_short_type["int"] = "int";
  InputOptionItem::get_short_type["long int"] = "long_int";
  InputOptionItem::get_short_type["double"] = "double";
  InputOptionItem::get_short_type["std::complex<double>"] = "complex";
  InputOptionItem::get_short_type["std::string"] = "string";
  InputOptionItem::get_short_type["std::vector<int>"] = "vec_int";
  InputOptionItem::get_short_type["std::vector<long int>"] = "vec_long_int";
  InputOptionItem::get_short_type["std::vector<double>"] = "vec_double";
  InputOptionItem::get_short_type["std::vector<std::complex<double> >"] = "vec_complex";
  InputOptionItem::get_short_type["std::vector<std::string>"] = "vec_string";

  InputOptionItem::get_const_func["bool"] = "bool";
  InputOptionItem::get_const_func["int"] = "int";
  InputOptionItem::get_const_func["long int"] = "(long int)";
  InputOptionItem::get_const_func["double"] = "double";
  InputOptionItem::get_const_func["std::complex<double>"] = "std::complex<double>";
  InputOptionItem::get_const_func["std::string"] = "std::string";
  InputOptionItem::get_const_func["std::vector<int>"] = "std::vector<int>";
  InputOptionItem::get_const_func["std::vector<long int>"] = "std::vector<long int>";
  InputOptionItem::get_const_func["std::vector<double>"] = "std::vector<double>";
  InputOptionItem::get_const_func["std::vector<std::complex<double> >"] = "std::vector<std::complex<double> >";
  InputOptionItem::get_const_func["std::vector<std::string>"] = "std::vector<std::string>";

  if (argc != 6) {
    std::cout << "Wrong number of arguments." << std::endl
              << "Usage:  input_options_generator option_list_file output_header_file output_cpp_file include_string namespace_name" << std::endl;
    return EXIT_FAILURE;
  }

  std::vector<InputOptionItem>::const_iterator option;
  std::vector<InputOptionItem> options;

  // read options
  {

    std::ifstream ofile(argv[1]);
    if ( !ofile.is_open() )
      throw formic::Exception("failed to open input options list file \"%s\"") % argv[1];

    try {

      while (true) {
        const InputOptionItem item(ofile);
        options.push_back(item);
      }

    } catch (InputOptionItem::NoMoreOptions & e) {
    }

    ofile.close();

  }

  // header file
  {

    // open the file
    std::ofstream hfile(argv[2], std::ios_base::out | std::ios_base::trunc);
    if ( !hfile.is_open() )
      throw formic::Exception("failed to open output header file \"%s\"") % argv[2];

    hfile << boost::format("#ifndef FORMIC_%s_INPUT_HEADER") % make_capital(argv[5]) << std::endl;
    hfile << boost::format("#define FORMIC_%s_INPUT_HEADER") % make_capital(argv[5]) << std::endl;
    hfile << "" << std::endl;
    hfile << "#include<formic/input/base.h>" << std::endl;
    hfile << "" << std::endl;
    hfile << "namespace formic {" << std::endl;
    hfile << "" << std::endl;
    hfile << boost::format("namespace %s {") % argv[5] << std::endl;
    hfile << "" << std::endl;
    hfile << "  class Input : public formic::InputBase {" << std::endl;
    hfile << "" << std::endl;
    hfile << "    protected:" << std::endl;
    hfile << "" << std::endl;
    hfile << "      void verify();" << std::endl;
    hfile << "" << std::endl;
    hfile << "      void initialize_derived();" << std::endl;
    hfile << "" << std::endl;
    for (option = options.begin(); option != options.end(); option++)
      option->write_header_declaration(hfile, "      ");
    hfile << "" << std::endl;
    hfile << "    public:" << std::endl;
    hfile << "" << std::endl;
    for (option = options.begin(); option != options.end(); option++)
      option->write_header_function(hfile, "      ");
    hfile << "" << std::endl;
    //hfile << "      void set_member(const std::string & member_name, formic::Archive & arch);" << std::endl;
    //hfile << "" << std::endl;
    hfile << "      Input();" << std::endl;
    hfile << "" << std::endl;
    hfile << "      std::string print_options();" << std::endl;
    hfile << "" << std::endl;
    hfile << "  };" << std::endl;
    hfile << "" << std::endl;
    hfile << boost::format("} // end namespace %s") % argv[5] << std::endl;
    hfile << "" << std::endl;
    hfile << "} // end namespace formic" << std::endl;
    hfile << "" << std::endl;
    hfile << "#endif" << std::endl;

    hfile.close();

  }

  // cpp file
  {

    // open the file
    std::ofstream cfile(argv[3], std::ios_base::out | std::ios_base::trunc);
    if ( !cfile.is_open() )
      throw formic::Exception("failed to open output cpp file \"%s\"") % argv[3];

    // includes
    cfile << "#include<algorithm>" << std::endl;
    cfile << "#include<vector>" << std::endl;
    cfile << "#include<map>" << std::endl;
    cfile << "#include<string>" << std::endl;
    cfile << "#include<sstream>" << std::endl;
    cfile << "#include<ctime>" << std::endl;
    cfile << "#include<cmath>" << std::endl;
    cfile << "" << std::endl;
    cfile << "#include<formic/exception.h>" << std::endl;
    cfile << "#include<formic/archive.h>" << std::endl;
    cfile << boost::format("#include<%s>") % argv[4] << std::endl;
    cfile << "" << std::endl;

    // default constructor
    cfile << boost::format("formic::%s::Input::Input() {") % argv[5] << std::endl;
    cfile << "" << std::endl;
    //if (options.size() > 0) {
    //  cfile << " :" << std::endl;
    //  cfile << "" << std::endl;
    //}
    //cfile << "{" << std::endl;
    for (option = options.begin(); option != options.end(); option++)
      option->write_cpp_default(cfile, "  ");
    cfile << "" << std::endl;
    cfile << "  // initialize derived data" << std::endl;
    cfile << "  this->initialize_derived();" << std::endl;
    //cfile << "  (dynamic_cast<formic::InputBase*>(this))->initialize_derived();" << std::endl;
    cfile << "" << std::endl;
    cfile << "  // verify that the input is sane" << std::endl;
    cfile << "  this->verify();" << std::endl;
    cfile << "" << std::endl;
    cfile << "}" << std::endl;
    cfile << "" << std::endl;

    //// set_member function
    //cfile << "void formic::Input::set_member(const std::string & member_name, formic::Archive & arch) {" << std::endl;
    //cfile << "" << std::endl;
    //cfile << "  if (member_name.size() == 0)" << std::endl;
    //cfile << "    throw formic::Exception(\"member_name was empty in formic::Input::set_member\");" << std::endl;
    //cfile << "" << std::endl;
    //for (option = options.begin(); option != options.end(); option++)
    //  option->write_cpp_set_member(cfile, "  ");
    //cfile << "  else" << std::endl;
    //cfile << "    throw formic::Exception(\"\\\"%s\\\" is not a valid input option name\") % member_name;" << std::endl;
    //cfile << "" << std::endl;
    //cfile << "  // initialized the derived data now that we have a new value for this member" << std::endl;
    //cfile << "  this->initialize_derived();" << std::endl;
    //cfile << "" << std::endl;
    //cfile << "  // verify that the input is sane" << std::endl;
    //cfile << "  this->verify();" << std::endl;
    //cfile << "" << std::endl;
    //cfile << "}" << std::endl;
    //cfile << "" << std::endl;

    // derivation function
    cfile << boost::format("void formic::%s::Input::initialize_derived() {") % argv[5] << std::endl;
    cfile << "" << std::endl;
    for (option = options.begin(); option != options.end(); option++)
      option->write_cpp_derivation(cfile, "  ");
    cfile << "}" << std::endl;
    cfile << "" << std::endl;

    // verify function
    cfile << boost::format("void formic::%s::Input::verify() {") % argv[5] << std::endl;
    cfile << "" << std::endl;
    for (option = options.begin(); option != options.end(); option++)
      option->write_cpp_tests(cfile, "  ");
    cfile << "}" << std::endl;
    cfile << "" << std::endl;

    // print function
    cfile << boost::format("std::string formic::%s::Input::print_options() {") % argv[5] << std::endl;
    cfile << "" << std::endl;
    cfile << "  std::stringstream ss;" << std::endl;
    cfile << "" << std::endl;
    for (option = options.begin(); option != options.end(); option++)
      option->write_cpp_print(cfile, "  ");
    cfile << "  return ss.str();" << std::endl;
    cfile << "" << std::endl;
    cfile << "}" << std::endl;
    cfile << "" << std::endl;

    cfile.close();

  }

  return EXIT_SUCCESS;

}
